
#region Imported Namespaces
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
#endregion

namespace PHSRAG.WebControls
{
    #region Class ExpandingPanel
    /// <summary>
    /// The ExpandingPanel is a control that supports two templates: one for a static view and another for a
    /// dynamic view. What you put in those templates is really up to you.
    /// The expand/collapse is done client-side.
    /// The ExpadningPanel works in concert with the ExpansionToggler control to manage the expansion/collapse activities.
    /// This binding is specified via the ExpansionToggler property which MUST be set to the ID of the ExpansionToggler
    /// control.
    /// </summary>
    /// <remarks>
    /// Pages which wish to display a static set of controls and a dynamic set of controls which is made visible
    /// (when a control is clicked upon) should use this control.
    /// </remarks>
    /// <example>
    /// <p>The following example shows how the page would use this control. The registration of the control can be done
    /// simply by dropping the control on the page in design mode.</p>
    /// <p>Notice the setting of the <b>ExpansionToggler</b> property to the ID of the ImageButton.</p>
    /// <code>
    /// <![CDATA[
    /// <%@ Register TagPrefix="cc1" Namespace="PHSRAG.WebControls" Assembly="PHSRAG.WebControls" %>
    /// ...
    /// <body>
    /// ...
    /// <cc1:ExpandingPanel id="expandingPanel" runat="server" ExpansionToggler="expansionToggler">
    ///		<StaticTemplate>
    ///			<table>
    ///				<tr>
    ///					<td>This is the static portion</td>
    ///				</tr>
    ///				<tr>
    ///					<td><cc1:ExpansionToggler id="expansionToggler" runat="Server"
    ///						CollapsedLinkCaption="Show Advanced Search Options"
    ///						ExpandedLinkCaption="Hide Advanced Search Options"
    ///						CollapsedImage="~/Images/Arrow_RT.gif"
    ///						ExpandedImage="~/Images/Arrow_DN.gif" />
    ///					</td>
    ///				</tr>
    ///			</table>
    ///		</StaticTemplate>
    ///		<DynamicTemplate>
    ///			<table>
    ///				<tr>
    ///					<td>This comes and goes...</td>
    ///				</tr>
    ///			</table>
    ///		</DynamicTemplate>
    /// </cc1:ExpandingPanel>
    /// ...
    /// </body>
    /// ]]>
    /// </code>
    /// </example>
    [ToolboxData("<{0}:ExpandingPanel runat=server />"), Designer(typeof(ExpandingPanelDesigner))]
    public class ExpandingPanel : System.Web.UI.WebControls.WebControl, INamingContainer
    {
        #region Constants
        private const string ClientScriptKey = "ClientScript_818BAFE9-4290-4c09-A5A9-383FCA22377B";
        private const string CollapseStyle = "none";
        private const string DynamicPanelID = "dynamicContainer";
        private const string ExpandedStyle = "block";
        private const string StaticPanelID = "staticContainer";
        #endregion

        #region Instance Variables
        private Panel dynamicPanel;
        private ITemplate dynamicTemplate;
        private string key = Guid.NewGuid().ToString();
        private static int instanceCount;
        private int instanceNumber;
        private bool preRendered;
        private Panel staticPanel;
        private ITemplate staticTemplate;
        #endregion

        #region Public Properties
        /// <summary>
        /// Get/Set the template which defines the static view.
        /// </summary>
        [Description("Get/Set the template which defines the static view"),
        PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(ExpandingPanel))]
        public ITemplate StaticTemplate
        {
            get { return staticTemplate; }
            set { staticTemplate = value; }
        }

        /// <summary>
        /// Set the ID of the ExpansionToggler control which instigates the expand/collapse.
        /// </summary>
        public string ExpansionToggler
        {
            get { return ViewState[ExpansionTogglerKey] as string; }
            set { ViewState[ExpansionTogglerKey] = value; }
        }

        /// <summary>
        /// Get/Set the template which defines the dynamic view.
        /// </summary>
        [Description("Get/Set the template which defines the dynamic view"),
        PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(ExpandingPanel))]
        public ITemplate DynamicTemplate
        {
            get { return dynamicTemplate; }
            set { dynamicTemplate = value; }
        }

        /// <summary>
        /// Get/Set the expanded state. First look at the ViewState to see if an explicit state is set, and if so, use that.
        /// Else, use the value from the field on the form which tracks the state.
        /// </summary>
        [Description("Get/Set the expanded state"),
        Bindable(true), DefaultValue(false), Category("Appearance")]
        public bool IsExpanded
        {
            get
            {
                bool expanded = false;
                if (!IsDesignMode)
                {
                    object state = ViewState[StateKey];
                    if (state == null)
                    {
                        state = HttpContext.Current.Request.Form.Get(ExpandedStateTrackerID);
                        if (state != null)
                            expanded = ((string)state == ExpandedStyle);
                    }
                    else
                        expanded = (bool)state;
                }
                return expanded;
            }
            set
            {
                ViewState[StateKey] = value;
            }
        }
        #endregion

        #region Private Properties
        // The following block of properties generate keys which are instance specific. This allows multiple instances
        // of the expanding panel to be used used on a page.

        private string ExpandedStateTrackerID { get { return string.Format("expandedStateTracker_{0}", instanceNumber); } }
        private string ExpansionTogglerKey { get { return string.Format("expansionToggler_{0}", key); } }
        private string StartupScriptKey { get { return string.Format("startupScript_{0}", key); } }
        private string StateKey { get { return string.Format("state_{0}", key); } }

        /// <summary>
        /// Get the design mode.
        /// </summary>
        private static bool IsDesignMode
        {
            get { return HttpContext.Current == null; }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Ensure that all child controls are created and return their collection.
        /// </summary>
        public override ControlCollection Controls
        {
            get { EnsureChildControls(); return base.Controls; }
        }

        /// <summary>
        /// Create all child controls of ExpandingPanel. The expanded/collapsed templates map to two panels within each
        /// of which the contents of the template are created. Switching from one to another is then as simple as
        /// toggling the visibility of each of those two panels (either client-side or server-side).
        /// </summary>
        protected override void CreateChildControls()
        {
            if (staticTemplate == null)
                throw new Exception("You MUST declare the StaticTemplate element");
            if (dynamicTemplate == null)
                throw new Exception("You MUST declare the DynamicTemplate element");

            Controls.Clear();

            Panel container = new Panel();
            container.ID = "container";
            container.CssClass = "ExpandingPanel";

            staticPanel = new Panel();
            staticPanel.ID = StaticPanelID;
            staticTemplate.InstantiateIn(staticPanel);
            container.Controls.Add(staticPanel);

            dynamicPanel = new Panel();
            dynamicPanel.ID = DynamicPanelID;
            dynamicTemplate.InstantiateIn(dynamicPanel);
            container.Controls.Add(dynamicPanel);

            Controls.Add(container);
        }

        /// <summary>
        /// Ensure that all child controls are created. We do this here so that they may be registered in the view state.
        /// </summary>
        /// <param name="e">Argument associated with the event</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnsureChildControls();
            Page.Unload += new EventHandler(OnPageUnload);
        }

        /// <summary>
        /// Initialize the expanded/collapsed panels.
        /// If client-side behavior (the default) is enabled, register the scripts that handle the toggling between
        /// expanded/collapsed states.
        /// </summary>
        /// <param name="e">Event arguments associated with the event</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (!IsDesignMode)
                instanceNumber = ++instanceCount;

            InitializePanels();
            bool isExpanded = IsExpanded;

            if (!IsDesignMode)
            {
                Helper.RegisterHiddenFieldWithId(Page, ExpandedStateTrackerID, isExpanded ? ExpandedStyle : CollapseStyle);
                RegisterClientScripts();
                ((ExpansionToggler)FindControl(ExpansionToggler)).SetControlState(isExpanded);
            }

            preRendered = true;
        }

        /// <summary>
        /// If the control has not been pre-rendered yet, do the rendering. The logic is very similar to that of the
        /// pre-render method.
        /// </summary>
        /// <param name="writer">The writer into which to render</param>
        protected override void Render(HtmlTextWriter writer)
        {
            if (!preRendered)
            {
                if (!IsDesignMode)
                    instanceNumber = ++instanceCount;
                InitializePanels();
            }

            base.Render(writer);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Ensure that the styles set on the ExpandingPanel propagates to both the expanded/collapsed panels.
        /// Set the visibility of the expanded panel based on the current state.
        /// </summary>
        private void InitializePanels()
        {
            dynamicPanel.MergeStyle(ControlStyle);
            staticPanel.MergeStyle(ControlStyle);

            SetPanelVisibility(staticPanel, true);
            SetPanelVisibility(dynamicPanel, IsExpanded);
        }

        /// <summary>
        /// Register the client scripts which will be used to toggle the expanded/collapsed state. There are two
        /// separate scripts: one to actually do the state toggling, and another to call on the first script during
        /// page load. The first script is static and is stored in the assmebly resource. The second script is created
        /// on-the-fly (since we need the client IDs of controls).
        /// </summary>
        private void RegisterClientScripts()
        {
            if (!Page.ClientScript.IsClientScriptBlockRegistered(ClientScriptKey))
                Page.ClientScript.RegisterClientScriptBlock(GetType(), ClientScriptKey, (new ResourceManager(GetType())).GetResourceSet(
                    System.Globalization.CultureInfo.CurrentCulture, true, true).GetString("ClientScript"));

            string key = StartupScriptKey;
            if (!Page.ClientScript.IsStartupScriptRegistered(key))
            {
                string script = string.Format(
                    "<script language='javascript'>\r\n" +
                    "LoadExpandingPanel('{0}','{1}', '{2}');\r\n" +
                    "</script>\r\n", FindControl(ExpansionToggler).ClientID, dynamicPanel.ClientID, ExpandedStateTrackerID);

                Page.ClientScript.RegisterStartupScript(GetType(), key, script);
            }
        }

        /// <summary>
        /// Set the visibility of the expanded/collapsed panels depending on the current state.
        /// </summary>
        /// <param name="panel">The panel whose visibility is to be set</param>
        /// <param name="visible">True to show the panel, false to hide</param>
        private static void SetPanelVisibility(Panel panel, bool visible)
        {
            if (!IsDesignMode)
                panel.Style["display"] = visible ? ExpandedStyle : CollapseStyle;
            else
                panel.Visible = visible;
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// This method is ONLY invoked by the associated designer to show the control contents at design time.
        /// </summary>
        internal void Design()
        {
            EnsureChildControls();
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// When the parent page unloads (last event) reset the static counter.
        /// </summary>
        /// <param name="sender">Control which raised the event</param>
        /// <param name="e">Event arguments associated with the event</param>
        private void OnPageUnload(object sender, EventArgs e)
        {
            instanceCount = 0;
        }
        #endregion
    }
    #endregion

    #region Class ExpandingPanelDesigner
    /// <summary>
    /// The ExpandingPanelDesigner helps in design-time activities of the ExpandingPanel control.
    /// </summary>
    /// <remarks>
    /// This class cannot be used directly.
    /// </remarks>
    public class ExpandingPanelDesigner : TemplatedControlDesigner
    {
        #region Instance Variables
        private TemplateEditingVerb[] templateEditingVerbs;
        #endregion

        #region Overrides
        /// <summary>
        /// Creates a template editing frame for the specified verb.
        /// </summary>
        /// <param name="verb">The template editing verb to create a template editing frame for</param>
        /// <returns></returns>
        [System.Obsolete]
        protected override ITemplateEditingFrame CreateTemplateEditingFrame(TemplateEditingVerb verb)
        {
            ITemplateEditingFrame frame = null;
            if (templateEditingVerbs != null)
            {
                ITemplateEditingService editingService = (ITemplateEditingService)GetService(typeof(ITemplateEditingService));
                if (editingService != null)
                {
                    Style style = ((ExpandingPanel)Component).ControlStyle;
                    string templateName = verb.Text.Replace(" ", "");
                    frame = editingService.CreateFrame(this, verb.Text, new string[] { templateName }, style, null);
                }
            }

            return frame;
        }

        /// <summary>
        /// Gets the cached template editing verbs
        /// </summary>
        /// <returns>An array of TemplateEditingVerb objects</returns>
        [System.Obsolete]
        protected override TemplateEditingVerb[] GetCachedTemplateEditingVerbs()
        {
            if (templateEditingVerbs == null)
            {
                templateEditingVerbs = new TemplateEditingVerb[2];
                templateEditingVerbs[0] = new TemplateEditingVerb("Static Template", 0, this);
                templateEditingVerbs[1] = new TemplateEditingVerb("Dynamic Template", 1, this);
            }

            return templateEditingVerbs;
        }

        /// <summary>
        /// Gets the HTML that is used to represent the control at design time.
        /// </summary>
        /// <returns>The HTML that is used to represent the control at design time</returns>
        public override string GetDesignTimeHtml()
        {
            ExpandingPanel panel = (ExpandingPanel)Component;
            if ((panel.DynamicTemplate == null) && (panel.StaticTemplate == null))
                return CreatePlaceHolderDesignTimeHtml("Right-click to edit the static/dynamic templates");

            panel.Design();
            return base.GetDesignTimeHtml();
        }

        /// <summary>
        /// Gets the template's content
        /// </summary>
        /// <param name="editingFrame">The template editing frame to retrieve the content of</param>
        /// <param name="templateName">The name of the template</param>
        /// <param name="allowEditing">true if the template's content can be edited; false otherwise</param>
        /// <returns>The content of the template</returns>
        [System.Obsolete]
        public override string GetTemplateContent(ITemplateEditingFrame editingFrame, string templateName, out bool allowEditing)
        {
            allowEditing = true;
            ExpandingPanel panel = (ExpandingPanel)Component;

            if (templateEditingVerbs != null)
                GetTextFromTemplate((templateName == "StaticTemplate") ? panel.StaticTemplate : panel.DynamicTemplate);
            return string.Empty;
        }

        /// <summary>
        /// Sets the specified template's content to the specified content
        /// </summary>
        /// <param name="editingFrame">The template editing frame to provide content for</param>
        /// <param name="templateName">The name of the template</param>
        /// <param name="templateContent">The content to set for the template</param>
        [System.Obsolete]
        public override void SetTemplateContent(ITemplateEditingFrame editingFrame, string templateName, string templateContent)
        {
            if (templateEditingVerbs != null)
            {
                ExpandingPanel panel = (ExpandingPanel)Component;
                ITemplate template = null;

                if ((templateContent != null) && (templateContent.Length != 0))
                    template = GetTemplateFromText(templateContent);

                if (templateName == "StaticTemplate")
                    panel.StaticTemplate = template;
                else
                    panel.DynamicTemplate = template;
            }
        }

        #endregion
    }
    #endregion

    #region Class ExpansionToggler
    /// <summary>
    /// The ExpansionToggler control is expected to work in concert with the ExpandingPanel control. Typically, the
    /// ExpandingPanel needs to know who instigates the expansion/collapse.
    /// </summary>
    /// <remarks>
    /// The ExpansionToggler control allows the use of an image button and/or a link button for display purposes.
    /// The image button is associated with user-specified (via the CollapsedImage/ExpandedImage properties) image file
    /// URLs, and the link button is associated with user-specified (via the CollapsedLinkCaption/ExpandedLinkCaption
    /// properties) captions.
    /// </remarks>
    [ToolboxData("<{0}:ExpansionToggler runat=server />")/*, Designer(typeof(ExpansionTogglerDesigner))*/]
    public class ExpansionToggler : System.Web.UI.WebControls.WebControl, INamingContainer
    {
        #region Constants
        private const string ClientScriptKey = "ClientScript_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string CollapsedImageKey = "CollapsedImage_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string CollapsedLinkCaptionKey = "CollapsedLinkCaption_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string ExpandedImageKey = "ExpandedImage_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string ExpandedLinkCaptionKey = "ExpandedLinkCaption_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string HelpTextKey = "HelpText_81BDEA9E-75FC-4ac5-9E72-EB480D326E72";
        private const string ImageButtonID = "imageButton";
        private const string LinkButtonID = "linkButton";
        private const string HelpTextID = "helpTextLabel";
        private const string OverviewTabsDisplayConditionMessage = "Specify one or more search criteria at the Summary level to limit the results in order to access the Overview tabs.";
        #endregion

        #region Instance Variables
        private ImageButton imageButton;
        private LinkButton linkButton;
        private Label helpText;
        private bool showOverviewHeaderHint;
        #endregion

        #region Public Properties
        /// <summary>
        /// Get/Set the collapsed-image URL for the image button.
        /// </summary>
        public string CollapsedImage
        {
            get { return ViewState[CollapsedImageKey] as string; }
            set { ViewState[CollapsedImageKey] = value; }
        }

        /// <summary>
        /// Get/Set the collapsed-caption text for the link button.
        /// </summary>
        public string CollapsedLinkCaption
        {
            get { return ViewState[CollapsedLinkCaptionKey] as string; }
            set { ViewState[CollapsedLinkCaptionKey] = value; }
        }

        /// <summary>
        /// Get/Set the caption text that appears next to the toggler image
        /// </summary>
        public string HelpText
        {
            get { return ViewState[HelpTextKey] as string; }
            set { ViewState[HelpTextKey] = value; }
        }

        /// <summary>
        /// Get/Set the expanded-image URL for the image button.
        /// </summary>
        public string ExpandedImage
        {
            get { return ViewState[ExpandedImageKey] as string; }
            set { ViewState[ExpandedImageKey] = value; }
        }

        /// <summary>
        /// Get/Set the expanded-caption text for the link button.
        /// </summary>
        public string ExpandedLinkCaption
        {
            get { return ViewState[ExpandedLinkCaptionKey] as string; }
            set { ViewState[ExpandedLinkCaptionKey] = value; }
        }

        /// <summary>
        /// Shows/hides the OverviewHeaderHint.
        /// </summary>
        public bool ShowOverViewHeaderHint
        {
            set { showOverviewHeaderHint = value; }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Ensure that all child controls are created and return their collection.
        /// </summary>
        public override ControlCollection Controls
        {
            get { EnsureChildControls(); return base.Controls; }
        }

        /// <summary>
        /// Create the image/link buttons.
        /// </summary>
        protected override void CreateChildControls()
        {
            Table table = new Table();
            Controls.Add(table);
            TableRow row = new TableRow();
            table.Rows.Add(row);

            string imageURL = CollapsedImage;
            if (imageURL != null)
            {
                TableCell cell = new TableCell();
                row.Cells.Add(cell);
                cell.Attributes["valign"] = "Top";
                cell.CssClass = "ExpansionToggler";

                imageButton = new ImageButton();
                imageButton.ID = ImageButtonID;
                imageButton.ImageUrl = imageURL;
                cell.Controls.Add(imageButton);
            }

            string linkCaption = CollapsedLinkCaption;
            if (linkCaption != null)
            {
                TableCell cell = new TableCell();
                row.Cells.Add(cell);
                cell.Attributes["valign"] = "Top";
                cell.CssClass = "ExpansionToggler";

                linkButton = new LinkButton();
                linkButton.ID = LinkButtonID;
                linkButton.Text = linkCaption;
                cell.Controls.Add(linkButton);
            }

            if (!String.IsNullOrEmpty(HelpText))
            {
                TableCell hintCell = new TableCell();
                row.Cells.Add(hintCell);
                hintCell.Attributes["valign"] = "Top";
                hintCell.CssClass = "ExpansionTogglerSearchHint";
                hintCell.Attributes["align"] = "left";

                helpText = new Label();
                helpText.ID = HelpTextID;
                helpText.Text = HelpText;
                hintCell.Controls.Add(new LiteralControl("&nbsp;&nbsp;&nbsp;&nbsp;"));
                hintCell.Controls.Add(helpText);
            }

            if ((imageButton == null) && (linkButton == null))
                throw new Exception("ExpansionToggler requires at least one pair of Caption/Image specified");
        }

        /// <summary>
        /// Associate client-side click handler for the image/link buttons and register a javascript function to manage
        /// toggling of the expanded/collapsed state. This javascript function is invoked by another javascript function
        /// associated with the ExpandingPanel.
        /// </summary>
        /// <param name="e">Argument associated with the event</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            string imageButtonID = (imageButton == null) ? string.Empty : imageButton.ClientID;
            string linkButtonID = (linkButton == null) ? string.Empty : linkButton.ClientID;

            if (imageButton != null)
            {
                if (Enabled)
                    imageButton.Attributes["onclick"] = string.Format("return ToggleExpandedState('{0}', '{1}', '{2}');",
                        ClientID, imageButtonID, linkButtonID);
                else
                    imageButton.Attributes["onclick"] = "return false;";
            }

            if (linkButton != null)
            {
                if (Enabled)
                    linkButton.Attributes["onclick"] = string.Format("return ToggleExpandedState('{0}', '{1}', '{2}');",
                        ClientID, imageButtonID, linkButtonID);
                else
                    linkButton.Attributes["onclick"] = "return false;";
            }
            if (helpText != null)
            {
                helpText.Text =
                (showOverviewHeaderHint) ? HelpText + "<br>&nbsp;&nbsp;&nbsp;&nbsp;" + OverviewTabsDisplayConditionMessage : HelpText;
            }

            // JavaScript doet not like paths that look like "~/something". It will prefix them with the protocol/server
            // like so: http://localhost/Insight/~/Images/foo.gif. So, we will replace the ~ with the application path
            // before passing them to the JavaScript function.

            if (!Page.ClientScript.IsClientScriptBlockRegistered(ClientScriptKey))
            {
                string appPath = Helper.GetApplicationPath(Page);
                appPath = appPath.TrimEnd('/');
                StringBuilder script = new StringBuilder();
                script.AppendFormat("<script language='javascript'>\r\n");

                if (imageButton != null)
                    script.AppendFormat(
                        "function SetImageButtonExpandedState(expand, buttonID)\r\n" +
                        "{{\r\n" +
                        "\tvar imageButton = document.getElementById(buttonID);\r\n" +
                        "\tif (imageButton != null)\r\n" +
                        "\t\timageButton.src = expand ? '{0}' : '{1}';\r\n" +
                        "}}\r\n", CollapsedImage.Replace("~", appPath), ExpandedImage.Replace("~", appPath));

                if (linkButton != null)
                    script.AppendFormat(
                        "function SetLinkButtonExpandedState(expand, buttonID)\r\n" +
                        "{{\r\n" +
                        "\tlinkButton = document.getElementById(buttonID);\r\n" +
                        "\tif (linkButton != null)\r\n" +
                        "\t\tlinkButton.innerHTML = expand ? '{0}' : '{1}';\r\n" +
                        "}}\r\n", CollapsedLinkCaption.Replace("~", appPath), ExpandedLinkCaption.Replace("~", appPath));
                script.AppendFormat("</script>\r\n");

                Page.ClientScript.RegisterClientScriptBlock(GetType(), ClientScriptKey, script.ToString());
            }
        }

        #endregion

        #region Internal Methods
        /// <summary>
        /// This method is invoked only by the ExpandingPanel control to set the expand/collapse state of the image/link.
        /// </summary>
        /// <param name="isExpanded">Current expanded state</param>
        internal void SetControlState(bool isExpanded)
        {
            EnsureChildControls();

            if (imageButton != null)
                imageButton.ImageUrl = isExpanded ? ExpandedImage : CollapsedImage;
            if (linkButton != null)
                linkButton.Text = isExpanded ? ExpandedLinkCaption : CollapsedLinkCaption;
        }
        #endregion
    }
    #endregion
}

// --- EOF ---
